<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>对象方法查看器</title>
    <style>
        body {
            font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
            display: flex;
            flex-direction: column;
            align-items: center;
            justify-content: center;
            min-height: 100vh;
            margin: 0;
            background-color: #f0f2f5;
            color: #333;
            padding: 20px;
            box-sizing: border-box;
        }

        .container {
            background-color: #ffffff;
            padding: 35px;
            border-radius: 15px;
            box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1);
            text-align: center;
            width: 90%;
            max-width: 800px;
            position: relative;
            overflow: hidden;
            border: 1px solid #e0e0e0;
        }

        h1 {
            color: #007bff;
            margin-bottom: 25px;
            font-size: 2.5em;
            font-weight: 700;
            text-shadow: 1px 1px 3px rgba(0, 0, 0, 0.05);
        }

        .info-text {
            font-size: 1.1em;
            color: #6c757d;
            margin-bottom: 30px;
            line-height: 1.6;
        }

        .controls {
            display: flex;
            flex-wrap: wrap;
            justify-content: center;
            gap: 20px;
            margin-bottom: 30px;
        }

        .controls label {
            font-weight: 600;
            color: #495057;
            margin-right: 10px;
        }

        .controls select,
        .controls input[type="checkbox"] {
            padding: 10px 15px;
            border-radius: 8px;
            border: 1px solid #ced4da;
            font-size: 1em;
            transition: border-color 0.3s ease, box-shadow 0.3s ease;
        }

        .controls select:focus,
        .controls input[type="checkbox"]:focus {
            border-color: #007bff;
            box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.25);
            outline: none;
        }

        .controls input[type="checkbox"] {
            transform: scale(1.2);
            margin-left: 5px;
        }

        .method-list-section {
            margin-top: 30px;
            text-align: left;
        }

        .method-list-section h2 {
            color: #28a745;
            font-size: 1.8em;
            margin-bottom: 15px;
            border-bottom: 2px solid #28a745;
            padding-bottom: 10px;
        }

        #methodList {
            list-style: none;
            padding: 0;
            max-height: 300px;
            overflow-y: auto;
            border: 1px solid #e9ecef;
            border-radius: 8px;
            background-color: #f8f9fa;
            padding: 10px;
        }

        #methodList li {
            background-color: #ffffff;
            margin-bottom: 8px;
            padding: 12px 15px;
            border-radius: 6px;
            display: flex;
            justify-content: space-between;
            align-items: center;
            box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
            transition: transform 0.2s ease, box-shadow 0.2s ease;
        }

        #methodList li:hover {
            transform: translateY(-2px);
            box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
        }

        #methodList li span {
            font-family: 'Consolas', 'Monaco', monospace;
            font-size: 1.1em;
            color: #0056b3;
            font-weight: 500;
        }

        #methodList li button {
            background-color: #6c757d;
            color: white;
            border: none;
            padding: 8px 15px;
            border-radius: 5px;
            cursor: pointer;
            font-size: 0.9em;
            transition: background-color 0.3s ease;
        }

        #methodList li button:hover {
            background-color: #5a6268;
        }

        .log-area-wrapper {
            margin-top: 30px;
            width: 100%;
            text-align: left;
        }

        .log-area-wrapper h2 {
            color: #dc3545;
            font-size: 1.8em;
            margin-bottom: 15px;
            border-bottom: 2px solid #dc3545;
            padding-bottom: 10px;
        }

        #logArea {
            background-color: #fff3cd;
            border: 1px solid #ffeeba;
            border-radius: 8px;
            padding: 15px;
            max-height: 200px;
            overflow-y: auto;
            font-family: 'Consolas', 'Monaco', monospace;
            font-size: 0.9em;
            color: #856404;
            text-align: left;
        }

        #logArea p {
            margin: 5px 0;
            border-bottom: 1px dashed #ffeeba;
            padding-bottom: 5px;
        }

        #logArea p:last-child {
            border-bottom: none;
        }

        .code-snippet {
            margin-top: 40px;
            background-color: #282c34;
            color: #abb2bf;
            border-radius: 10px;
            padding: 20px;
            text-align: left;
            font-family: 'Fira Code', 'Consolas', 'Monaco', monospace;
            font-size: 0.9em;
            overflow-x: auto;
            box-shadow: 0 5px 15px rgba(0, 0, 0, 0.2);
        }

        .code-snippet pre {
            margin: 0;
        }

        .code-snippet code {
            display: block;
            white-space: pre-wrap;
        }

        .code-snippet .comment {
            color: #5c6370;
        }

        .code-snippet .keyword {
            color: #c678dd;
        }

        .code-snippet .function-name {
            color: #61afef;
        }

        .code-snippet .variable {
            color: #e5c07b;
        }

        .code-snippet .string {
            color: #98c379;
        }

        .code-snippet .operator {
            color: #c678dd;
        }
    </style>
</head>

<body>
    <div class="container">
        <h1>JavaScript 对象方法查看器</h1>
        <p class="info-text">
            本工具演示了如何使用 <code>functions</code> 辅助函数来动态地列出一个JavaScript对象的所有方法（函数）。
            您可以选择不同的预定义对象，并决定是否包含继承的方法。点击方法名称旁边的“调用”按钮，
            可以在控制台和日志区域观察该方法的执行。
        </p>

        <div class="controls">
            <label for="objectSelect">选择对象:</label>
            <select id="objectSelect">
                <option value="foo">Foo 实例</option>
                <option value="math">Math 对象</option>
                <option value="date">Date 实例</option>
                <option value="array">Array 实例</option>
            </select>

            <label>
                <input type="checkbox" id="inheritedMethodsCheckbox">
                包含继承方法
            </label>
        </div>

        <div class="method-list-section">
            <h2>可用方法</h2>
            <ul id="methodList">
                <!-- 方法将在这里动态加载 -->
            </ul>
        </div>

        <div class="log-area-wrapper">
            <h2>操作日志</h2>
            <div id="logArea">
                <p><strong>系统:</strong> <span>请选择一个对象来查看其方法。</span></p>
            </div>
        </div>

        <div class="code-snippet">
            <p><code>functions</code> 核心代码片段:</p>
            <pre><code>
<span class="keyword">const</span> <span class="function-name">functions</span> = (<span class="variable">obj</span>, <span class="variable">inherited</span> = <span class="keyword">false</span>) =>
    (<span class="variable">inherited</span>
        ? [...<span class="function-name">Object</span>.<span class="function-name">keys</span>(<span class="variable">obj</span>), ...<span class="function-name">Object</span>.<span class="function-name">keys</span>(<span class="function-name">Object</span>.<span class="function-name">getPrototypeOf</span>(<span class="variable">obj</span>))]
        : <span class="function-name">Object</span>.<span class="function-name">keys</span>(<span class="variable">obj</span>)
    ).<span class="function-name">filter</span>(<span class="variable">k</span> => <span class="keyword">typeof</span> <span class="variable">obj</span>[<span class="variable">k</span>] <span class="operator">===</span> <span class="string">'function'</span>);
            </code></pre>
        </div>
    </div>

    <script>
        // 核心函数：用于获取对象的所有函数名称
        const functions = (obj, inherited = false) =>
            (inherited
                ? [...Object.keys(obj), ...Object.keys(Object.getPrototypeOf(obj))]
                : Object.keys(obj)
            ).filter(k => typeof obj[k] === 'function');

        // 示例对象
        function Foo() {
            this.a = () => "Foo.a() called";
            this.b = (x, y) => `Foo.b(${x}, ${y}) called`;
            this.data = 10;
        }
        Foo.prototype.c = () => "Foo.prototype.c() called";
        Foo.prototype.d = (val) => `Foo.prototype.d(${val}) called`;

        const myFoo = new Foo();
        const myDate = new Date();
        const myArray = [1, 2, 3];

        // UI 元素
        const objectSelect = document.getElementById('objectSelect');
        const inheritedMethodsCheckbox = document.getElementById('inheritedMethodsCheckbox');
        const methodList = document.getElementById('methodList');
        const logArea = document.getElementById('logArea');

        // 日志输出到页面
        function logToArea(message, type = 'info') {
            const p = document.createElement('p');
            const time = new Date().toLocaleTimeString();
            p.innerHTML = `<strong>${time} [${type.toUpperCase()}]:</strong> <span>${message}</span>`;
            logArea.appendChild(p);
            logArea.scrollTop = logArea.scrollHeight; // 滚动到底部
        }

        // 更新方法列表
        function updateMethodList() {
            methodList.innerHTML = ''; // 清空现有列表
            const selectedObjectKey = objectSelect.value;
            const includeInherited = inheritedMethodsCheckbox.checked;

            let currentObject;
            switch (selectedObjectKey) {
                case 'foo':
                    currentObject = myFoo;
                    break;
                case 'math':
                    currentObject = Math;
                    break;
                case 'date':
                    currentObject = myDate;
                    break;
                case 'array':
                    currentObject = myArray;
                    break;
                default:
                    currentObject = {};
            }

            const availableFunctions = functions(currentObject, includeInherited);

            if (availableFunctions.length === 0) {
                const li = document.createElement('li');
                li.textContent = '没有找到任何方法。';
                methodList.appendChild(li);
                logToArea(`对象 '${selectedObjectKey}' 没有找到任何方法。`, 'warn');
                return;
            }

            availableFunctions.forEach(funcName => {
                const li = document.createElement('li');
                const span = document.createElement('span');
                span.textContent = funcName;
                li.appendChild(span);

                const callButton = document.createElement('button');
                callButton.textContent = '调用';
                callButton.onclick = () => callMethod(currentObject, funcName);
                li.appendChild(callButton);

                methodList.appendChild(li);
            });
            logToArea(`已加载对象 '${selectedObjectKey}' 的 ${availableFunctions.length} 个方法。`);
        }

        // 调用方法
        function callMethod(obj, funcName) {
            try {
                const method = obj[funcName];
                let result;
                let args = [];

                // 根据方法名提供一些示例参数
                if (obj === myFoo) {
                    if (funcName === 'b') args = [10, 20];
                    if (funcName === 'd') args = ['test_value'];
                } else if (obj === Math) {
                    if (funcName === 'max') args = [1, 5, 2];
                    if (funcName === 'random') args = [];
                    if (funcName === 'pow') args = [2, 3];
                } else if (obj === myDate) {
                    if (funcName === 'setFullYear') args = [2025];
                    if (funcName === 'toLocaleString') args = ['zh-CN'];
                } else if (obj === myArray) {
                    if (funcName === 'push') args = [4];
                    if (funcName === 'pop') args = [];
                    if (funcName === 'join') args = [', '];
                }

                result = method.apply(obj, args);

                console.log(`调用 ${obj.constructor.name || typeof obj}.${funcName}(${args.join(', ')}) 结果:`, result);
                logToArea(`成功调用 <code>${obj.constructor.name || typeof obj}.${funcName}(${args.map(a => typeof a === 'string' ? `'${a}'` : a).join(', ')})</code>. 结果: <code>${JSON.stringify(result)}</code>`);
            } catch (error) {
                console.error(`调用 ${funcName} 失败:`, error);
                logToArea(`调用 <code>${funcName}</code> 失败: ${error.message}`, 'error');
            }
        }

        // 事件监听
        objectSelect.addEventListener('change', updateMethodList);
        inheritedMethodsCheckbox.addEventListener('change', updateMethodList);

        // 初始加载
        updateMethodList();
    </script>
</body>

</html>